Skip to content

perf(pack): isolate multi-page endpoints - #3297

Merged
fireairforce merged 4 commits into
nextfrom
zoomdong/perf-multipage-endpoints
Aug 14, 2026
Merged

perf(pack): isolate multi-page endpoints#3297
fireairforce merged 4 commits into
nextfrom
zoomdong/perf-multipage-endpoints

Conversation

@fireairforce

@fireairforce fireairforce commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

Utoopack previously aggregated all regular app entries into one AppEndpoint, which prevented page-level scheduling and made each generated HTML file include assets from every page.

This PR now:

  • creates one AppEndpoint per regular app entry while sharing the project module/runtime contexts;
  • builds independent Client and plain server.entry work concurrently; server.function still intentionally scans all Client graphs to discover server references;
  • keeps copy assets and the memoized Server output reachable from every endpoint, so writing any page independently preserves shared outputs without rebuilding them;
  • generates entry HTML from its owning endpoint(s), groups multiple module scripts that target the same HTML file, and keeps global HTML asset order deterministic;
  • serializes/coalesces HTML writes so concurrent HMR updates cannot overwrite newer asset sets;
  • reduces dev NAPI subscriptions from 2N to N for browser-only/Node targets, and to N+1 for browser apps with shared Server output;
  • fails fast when the endpoint/config cardinality contract diverges;
  • scopes development stats chunk lists to their owning endpoint instead of attaching every page bootstrap to every stats entrypoint.

Observable API note: entrypoints.apps now contains one endpoint per ordinary app entry instead of one aggregate endpoint. The endpoint order remains the filtered config entry order. A configured global HTML file still needs the union of all endpoint assets, so regenerating that single global file remains O(N), but concurrent updates are coalesced into one ordered writer.

Paired clean-build benchmarks with persistent cache disabled:

Scenario Before After Change
1-page production 21.93 ms 21.57 ms ~flat
32-page production 48.24 ms 37.12 ms ~23% faster
32-page dev + HTML 30.76 ms 17.49 ms ~43% faster
128-page pure dev 22.77 ms 21.04 ms ~7.6% faster
20-page production + copy + server.entry (follow-up vs original PR head) ~180 ms ~155 ms ~14% faster

For the 32-page HTML fixture, each page now includes one owning entry script instead of all 32 entry scripts. A real 19-page EVJS MPA likewise dropped from 19 page entry scripts per HTML file to 1; all 19 pages returned HTTP 200, and page HMR remained scoped after rebuild.

Test Plan

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings --no-deps
  • UPDATE=1 cargo test -p pack-tests --test snapshot multi_page_shared_outputs -- --ignored --nocapture
    • verifies two app endpoints;
    • writes each endpoint independently;
    • verifies each contains copy output and the shared Server bundle;
    • verifies the shared Server bundle contains a Server Function discovered from the second page.
  • npm test --workspace @utoo/pack (55 passed)
    • covers HTML ordering/grouping/concurrent writes, browser/Node watcher counts, and per-entrypoint dev stats ownership.
  • npm run build:js --workspace @utoo/pack
  • npx biome ci
  • npx tsc -p packages/pack/tsconfig.json --noEmit
  • Paired release-local benchmark for 20-page production with copy + server.entry, persistent cache disabled.

@fireairforce
fireairforce marked this pull request as ready for review August 14, 2026 06:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc2e52c047

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/pack/src/core/hmr.ts Outdated
@fireairforce
fireairforce force-pushed the zoomdong/perf-multipage-endpoints branch from f7814bf to 37b9d25 Compare August 14, 2026 08:03
@github-actions

Copy link
Copy Markdown

📊 Performance Benchmark Report (with-antd)

Utoopack Performance Report

Report ID: utoopack_performance_report_20260814_082036
Generated: 2026-08-14 08:20:36
Trace File: trace_antd.json (0.4GB, 1.13M spans)
Test Project: examples/with-antd


Executive Summary

Metric Value Assessment
Total Wall Time 6,807.6 ms Baseline
Total Thread Work (de-duped) 20,387.0 ms Non-overlapping busy time
Effective Parallelism 3.0x thread_work / wall_time
Working Threads 10 Threads with actual spans
Thread Utilization 29.9% ⚠️ Suboptimal
Total Spans 1,125,880 All B/E + X events
Meaningful Spans (>= 10us) 349,779 (31.1% of total)
Tracing Noise (< 10us) 776,101 (68.9% of total)

Build Phase Timeline

Shows when each build phase is active and how much CPU it consumes.
Self-Time is the time spent exclusively in that phase (excluding children).

Phase Spans Inclusive (ms) Self-Time (ms) Wall Range (ms)
Resolve 91,700 2,439.1 1,906.7 3,581.1
Parse 10,034 1,062.0 1,005.1 6,144.5
Analyze 224,185 12,049.9 8,540.6 6,048.1
Chunk 8,202 2,812.8 823.6 2,250.1
Codegen 9,837 1,455.2 1,378.2 1,933.2
Emit 34 42.0 21.0 10.0
Other 5,787 6,411.5 3,876.9 6,807.6

Workload Distribution by Diagnostic Tier

Category Spans Inclusive (ms) % Work Self-Time (ms) % Self
P0: Scheduling & Resolution 318,728 14,810.9 72.6% 10,657.6 52.3%
P1: I/O & Heavy Tasks 2,891 112.6 0.6% 91.6 0.4%
P2: Architecture (Locks/Memory) 0 0.0 0.0% 0.0 0.0%
P3: Asset Pipeline 26,577 5,350.0 26.2% 3,227.0 15.8%
P4: Bridge/Interop 0 0.0 0.0% 0.0 0.0%
Other 1,583 5,998.9 29.4% 3,575.9 17.5%

Top 20 Tasks by Self-Time

Self-time is the exclusive duration: time spent in the task itself, not in sub-tasks.
This is the most accurate indicator of where CPU cycles are actually spent.

Self (ms) Inclusive (ms) Count Avg Self (us) P95 Self (ms) Max Self (ms) % Work Task Name Top Caller
4,602.8 6,504.4 143,227 32.1 0.1 15.6 22.6% module write all entrypoints to disk (1%)
2,428.9 2,544.3 25 97156.7 422.2 568.0 11.9% save snapshot persist (4%)
1,923.0 1,958.2 25,450 75.6 0.2 224.5 9.4% analyze ecmascript module module (66%)
1,171.1 1,266.9 50,934 23.0 0.0 3.6 5.7% internal resolving resolving (29%)
1,150.5 2,692.1 46,944 24.5 0.0 4.0 5.6% process module module (20%)
948.8 1,005.7 7,865 120.6 0.5 32.5 4.7% parse ecmascript process module (25%)
732.9 732.9 7,462 98.2 0.4 6.7 3.6% precompute code generation generate merged code (51%)
721.9 721.9 5,927 121.8 0.5 103.0 3.5% compute async module info compute merged modules (0%)
721.2 1,157.8 40,078 18.0 0.0 4.3 3.5% resolving module (31%)
655.3 2,643.4 6,472 101.3 0.1 42.1 3.2% chunking compute async chunks (0%)
629.2 1,931.1 1,312 479.6 1.1 274.4 3.1% generate merged code chunking (69%)
427.2 427.2 329 1298.4 1.3 244.7 2.1% generate source map code generation (83%)
370.3 745.7 173 2140.7 4.5 191.6 1.8% emit code generate merged code (32%)
277.1 384.0 3,791 73.1 0.0 126.9 1.4% write all entrypoints to disk None (0%)
218.2 295.1 2,046 106.6 0.2 64.4 1.1% code generation chunking (11%)
165.7 166.9 1,684 98.4 0.1 17.8 0.8% compute async chunks write all entrypoints to disk (0%)
125.1 125.1 13 9624.1 53.7 56.6 0.6% blocking save snapshot (62%)
76.4 76.4 673 113.5 0.0 21.3 0.4% compute binding usage info write all entrypoints to disk (0%)
56.2 56.2 2,166 26.0 0.0 3.1 0.3% read file parse ecmascript (91%)
52.0 52.0 1,902 27.3 0.0 12.7 0.3% collect mergeable modules compute merged modules (2%)

Critical Path Analysis

The longest sequential dependency chains that determine wall-clock time.
Focus on reducing the depth of these chains to improve parallelism.

Rank Self-Time (ms) Depth Path
1 624.6 3 persist → save snapshot → blocking
2 466.0 3 chunking → generate merged code → emit code
3 432.9 2 save snapshot → blocking
4 291.3 4 chunking → generate merged code → emit code → generate source map
5 224.5 2 process module → analyze ecmascript module

Batching Candidates

High-volume tasks dominated by a single parent. If the parent can batch them,
it drastically reduces scheduler overhead.

Task Name Count Top Caller (Attribution) Avg Self P95 Self Total Self
No obvious batching candidates found - - - - -

Duration Distribution

Range Count Percentage
<10us 776,101 68.9%
10us-100us 331,090 29.4%
100us-1ms 15,442 1.4%
1ms-10ms 3,138 0.3%
10ms-100ms 89 0.0%
>100ms 20 0.0%

Action Items

  1. [P0] Focus on tasks with the highest Self-Time — these are where CPU cycles are actually spent.
  2. [P0] Use Batching Candidates to identify callers that should use try_join or reduce #[turbo_tasks::function] granularity.
  3. [P1] Check Build Phase Timeline for phases with disproportionate wall range vs. self-time (= serialization).
  4. [P1] Inspect P95 Self (ms) for heavy monolith tasks. Focus on long-tail outliers, not averages.
  5. [P1] Review Critical Paths — reducing the longest chain depth directly improves wall-clock time.
  6. [P2] If Thread Utilization < 60%, investigate scheduling gaps (lock contention or deep dependency chains).

Report generated by Utoopack Performance Analysis Agent

@fireairforce
fireairforce merged commit 080b12e into next Aug 14, 2026
48 checks passed
@fireairforce
fireairforce deleted the zoomdong/perf-multipage-endpoints branch August 14, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants